【译】在 vue 中使用各种各样的 javascript 库

Use Any Javascript Library With Vue.js – Vue.js Developers – Medium

Lodash, Moment, Axios, Async…these are useful Javascript libraries that you’ll want to utilise in many of your Vue.js apps.

Lodash, Moment, Axios, Async…这些都是非常有用的 Javascript 库,而且你会希望在你的各种 Vue.js 应用之中使用。

But as your project grows you’ll be separating code into single file components and module files. You also may want to run your app in different environments to allow server rendering.

但是随着你的项目的增长,你会想要将代码分离为单一的组件文件跟模块文件。同时,你可能也会想要让你的应用能运行在不同的环境下,比如可以通过服务端渲染。

Unless you find an easy and robust way to include those Javascript libraries across your components and module files they’re going to be a nuisance!

除非你找到一个简单且健壮的方法去引入这些 Javascript 库到你的组件文件与模块文件中,不然他们将会成为你项目中的一个累赘!

Note: this article was originally posted here on the Vue.js Developers blog on 2017/04/22

_注意:这篇文章原载于the Vue.js Developers blog 2017/04/22_

How to include a library in a Vue.js project

如何引入一个类库到你的 Vue.js 项目中

Global variable

全局变量

最直接添加一个类库到你的项目中的方法,是让这个类库作为一个全局变量挂载在 window 对象上
The naive way to add a library to your project is to make it a global variable by attaching it to the window object:

entry.js

1
window._ = require('lodash');

MyComponent.vue

1
2
3
4
5
export default {
created() {
console.log(_.isEmpty() ? 'Lodash everywhere!' : 'Uh oh..');
}
}

关于反对 window 全局变量是一个十分悠久的话题,但是,在具体到这篇文章中,是因为这样不支持服务器渲染,当这个应用跑在服务端,window 对象将不复存在,因此会导致尝试访问这个原型的时候会抛出一个错误并终止它。
The case against window variables is a long one, but, specifically to this discussion, they don’t work with server rendering. When the app runs on the server the window object will be undefined and so attempting to access a property will end with an error.

Importing in every file

引入到每个文件中

另一个二流方法是在每一个文件中都把类库文件引入进去。
Another second-rate method is to import the library into every file:

MyComponent.vue

1
2
3
4
5
6
7
import _ from 'lodash';
export default {
created() {
console.log(_.isEmpty() ? 'Lodash is available here!' : 'Uh oh..');
}
}

这当然行得通,然而这并不 DRY 并且这基本上就是一种惩罚游戏:你不得不记住要在每个文件中都引入这些些类库,以及当你不再需要使用它以后也要记得移除它。
与此同时,如果你没有正确的设置好你的构建工具,你很有可能会在你最终构建出来的文件中会有一个类库的多个副本在其中。
This works, but it’s not very DRY and it’s basically just a pain: you have to remember to import it into every file, and remove it again if you stop using it in that file. And if you don’t setup your build tool correctly you may end up with multiple copies of the same library in your build.

A better way

一个更好的解决方案

在 Vue 项目中使用一个 Javascript 库的最干净且最健壮的方法是将他代理为 Vue 原型对象的属性。
让我们来试试通过这种方式把 Moment date 和 time 库添加到我们的项目中来:
The cleanest and most robust way to use a Javascript library in a Vue project is to proxy it to a property of the Vue prototype object. Let’s do that to add the Moment date and time library to our project:

entry.js

1
2
import moment from 'moment';
Object.definePrototype(Vue.prototype, '$moment', { value: moment });

由于所有的组件都会从 Vue 原型对象中继承其所有方法,这让 Moment 自然而然的传递到任何或者说所有的组件当中,并且无需全局变量或者是手动引入什么东西(到组件中)。
只通过 this.$moment 就可以在任意的实例/组件中快速简单地访问到 Moment

Since all components inherit their methods from the Vue prototype object this will make Moment automatically available across any and all components with no global variables or anything to manually import. It can simply be accessed in any instance/component from this.$moment:

MyNewComponent.vue

1
2
3
4
5
export default {
created() {
console.log('The time is ' . this.$moment().format("HH:mm"));
}
}

现在,让我们花点时间来搞清楚这是为什么。
Let’s take the time now to understand how this works.

Object.defineProperty

我们通常会这样设置一个对象属性:
We would normally set an object property like this:

1
Vue.prototype.$moment = moment;

在这个示例中你确实可以这样做,但如果用Object.defineProperty来定义,我们就可以同时定义属性的描述符。描述符允许我们设置一些底层的细节信息,例如我们的属性是否是可写的,又或者是循环中是否是可枚举的等等。

You could do that here, but by using Object.defineProperty instead we are able to define our property with a descriptor. A descriptor allows us to set some low-level details such as whether or not our property is writeable and whether it shows up during enumeration in a for loop and more.

我们通常不会对此感兴趣,因为在我们日复一日的 Javascript 时间里,其中 99% 的时间我们都不需要了解属性赋值的这一低层级细节信息。
不过在这里它可以给我们明显的好处:通过描述符去创建的属性默认是只读的

We don’t normally bother with this in our day-to-day Javascript because 99% of the time we don’t need that level of detail with a property assignment. But here it gives us a distinct advantage: properties created with a descriptor are read-only by default.

这意味着那些一时失了智的开发者(或者就是你自己)不会有机会干出一些类似下面这个组件中那样的蠢事,进而搞坏整锅粥。
This means that some coffee-deprived developer (probably you) won’t be able to do something silly like this in a component and break everything:

1
2
this.$http = 'Assign some random thing to the instance method';
this.$http.get('/'); // TypeError: this.$http.get is not a function

相对的,我们的只读实例方法保护了我们的类库不被修改,假如你尝试去重写它,你就只会得到一个“TypeError: Cannot assign to read only property”。
Instead, our read-only instance method protects our library, and if you attempt to overwrite it you will get “TypeError: Cannot assign to read only property”.

$

你应该会注意到代理我们的类库到一个属性上的时候,属性名前补充了一个美元符号“$”的前缀。你也大概已经见过其他一些属性或者方法比如 $refs,$on,$mount 等等也都有同样的前缀。
You’ll notice that we proxy our library to a property name prefixed with the dollar sign “$”. You’ve probably also seen other properties and methods like $refs, $on, $mount etc which have this prefix too.

虽然不是必须的,这个前缀加到这些属性上也是为了提醒那些失了智的开发者(不用看,还是你),这是一个公共 API 属性或者方法,欢迎使用,不像其他的实例属性可能只是给 Vue 的内部使用。

While not required, the prefix is added to properties to remind coffee-deprived developers (you, again) that this is a public API property or method that you’re welcome to use, unlike other properties of the instance that are probably just for Vue’s internal use.

作为一个基于原型链的语言,Javascript 没有(真正意义上的)类,因此也没有所谓的 “私有” 和 “公共” 变量或者是 “静态” 方法。
而这种($ 符号)约定俗成实际上是一种柔和的提醒,我认为这是值得去遵循的。
Being a prototype-based language, there are no (real) classes in Javascript so it doesn’t have “private” and “public” variables or “static” methods. This convention is a mild substitute which I think is worthwhile to follow.

this

由于类库现在是一个实例方法,所以通过 this.libraryName 来使用类库不会是一件值得惊讶的事。
You’ll also notice that to use the library you use this.libraryName which is probably not a surprise since it is now an instance method.

这样做的一个后果是,与使用全局变量不一样,你必须确保使用类库时处于一个正确的作用于中。
比如在内部回调函数中你就不能访问的到 this 上的类库。
One consequence of this, though, is that unlike a global variable you must ensure you’re in the correct scope when using your library. Inside callback methods you can’t access the this that your library inhabits.

对此,箭头回调函数会是一个不错的解决方案,它会确保你在正确的作用域中
Fat arrow callbacks are a good solution to making sure you stay in the right scope:

1
2
3
4
5
6
this.$http.get('/').then(res => {
if (res.status !== 200) {
this.$http.get('/') // etc
// Only works in a fat arrow callback.
}
});

Why not make it a plugin?

为什么不把它作为一个插件呢?

如果你计划在多个 Vue 项目中使用同一个类库,又或者你想要把它分享给全世界,那么你其实可以去构建一个属于你自己的插件。
If you’re planning to use a library across many Vue projects, or you want to share it with the world, you can build this into your own plugin!

一个插件可以把复杂的操作抽象出来,从而允许你通过如下面所展示,十分简单的方式去把你所选的类库添加到一个项目中。
A plugin abstracts complexity and allows you to simply do the following in a project to add your chosen library:

1
2
import MyLibraryPlugin from 'my-library-plugin';
Vue.use(MyLibraryPlugin);

仅靠这样两行代码,我们就可以在任意的组件中使用我们的类库,就像我们使用 Vue Router,Vuex 又或者其他可以通过 Vue.use 使用的插件那样。
With these two lines we can use the library in any component just like we can with Vue Router, Vuex and other plugins that utilise Vue.use.

Writing a plugin

写一个插件

首先,为你的插件创建一个文件。在这个例子中我将会写一个把 Axios 添加到你所有的 Vue 示例和组件的插件,因此我将文件命名为 axios.js
Firstly, create a file for your plugin. In this example I’ll make a plugin that adds Axios to your all your Vue instances and components, so I’ll call the file axios.js.

其中最需要搞清的事情是,插件需要暴露一个 install 方法,该方法中会将 Vue 的构造函数作为第一个参数。
The main thing to understand is that a plugin must expose an install method which takes the Vue constructor as the first argument:

axios.js

1
2
3
4
5
export default {
install: function(Vue) {
// Do stuff
}
}

现在我们可以使用我们私有的方法去把类库添加到原型对象:
Now we can use our previos method to add the library to the prototype object:

axios.js

1
2
3
4
5
6
7
import axios from 'axios';
export default {
install: function(Vue,) {
Object.defineProperty(Vue.prototype, '$http', { value: axios });
}
}

对于添加类库到一个项目中作业这个目标来说,use 这个实例方法就是我们现在所需要知道的一切。
举个例子,要把 Axios 添加到项目中,我们只需要像下面那样容易操作:
The use instance method is all we now need to add our library to a project. For example, we can now add the Axios library as easily as this:

entry.js

1
2
3
4
5
6
7
8
import AxiosPlugin from './axios.js';
Vue.use(AxiosPlugin);
new Vue({
created() {
console.log(this.$http ? 'Axios works!' : 'Uh oh..');
}
})

Bonus: plugin optional arguments

砰: 插件的可选参数

你的插件的 install 方法还可以带上可选参数。一些开发者可能不会喜欢命名他们的 Axios 实例方法为 $http ,这是由于 Vue Resource 通常会使用这个名字,
那么,让我们通过可选参数来允许他们可以改用任何他们喜欢的名字吧:

Your plugin install method can take optional arguments. Some devs might not like calling their Axios instance method $http since Vue Resource is commonly given that name, so let’s use an optional argument to allow them to change it to whatever they like:

axios.js

1
2
3
4
5
6
7
import axios from 'axios';
export default {
install: function(Vue, name = '$http') {
Object.defineProperty(Vue.prototype, name, { value: axios });
}
}

entry.js

1
2
3
4
5
6
7
8
import AxiosPlugin from './axios.js';
Vue.use(AxiosPlugin, '$axios');
new Vue({
created() {
console.log(this.$axios ? 'Axios works!' : 'Uh oh..');
}
})


获得最新的 Vue.js 文章,教程和最棒的项目,都在这里 Vue.js Developers Newsletter